
#include <sstream>
#include <string>
#include <d3dx9.h>
#include "dxstdafx.h"

#include "mmgr.h"

#include "Sky.h"
#include "Ocean.h"

struct SGroundVertex
{
	float x, y, z;
	DWORD diffuse;
	float tu, tv;
};

#define D3DFVF_GROUNDVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)

//Global Objects
LPDIRECT3DVERTEXBUFFER9    pGroundVertexBuffer;
LPDIRECT3DINDEXBUFFER9     pGroundIndexBuffer;
CDXUTDialog                hud;
CDXUTDialogResourceManager dialogResourceManager; //Manager for shared resources of dialogs
CFirstPersonCamera         camera;
CSky					   sky;

//States
bool showHud     = true;
bool enableNVHUD = false;

//Helper functions
bool initD3DDevice();      //Custom function to allow use of NVPerfHud
bool initHUD();
bool initGround();

//Callbacks
bool    CALLBACK IsDeviceAcceptable(D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat, bool bWindowed, void* pUserContext);
bool    CALLBACK ModifyDeviceSettings(DXUTDeviceSettings* pDeviceSettings, const D3DCAPS9* pCaps, void* pUserContext);
HRESULT CALLBACK OnCreateDevice(IDirect3DDevice9* pD3DDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext);
HRESULT CALLBACK OnResetDevice(IDirect3DDevice9* pD3DDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext);
void    CALLBACK OnFrameMove( IDirect3DDevice9* pdD3Device, double fTime, float fElapsedTime, void* pUserContext);
void    CALLBACK OnFrameRender( IDirect3DDevice9* pD3DDevice, double fTime, float fElapsedTime, void* pUserContext);
void    CALLBACK OnLostDevice(void* pUserContext);
void    CALLBACK OnDestroyDevice(void* pUserContext);
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing, void* pUserContext);
void    CALLBACK OnGUIEvent(UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext);

//UI control IDs
enum CONTROL_IDS
{
	IDC_HIDEINTERFACE,
	IDC_TOGGLEFULLSCREEN,
	IDC_TAKESCREENSHOT,
	IDC_FPSDISPLAY,
	IDC_SKYSPEED,
	IDC_ENABLESKYTIME,
	IDC_SKYSPEEDPAUSE,
	IDC_SKYTIMESLIDER,
};

//Main function
INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
{
	// Enable run-time memory check for debug builds.
	#if defined(DEBUG) | defined(_DEBUG)
		_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
	#endif

	//Set the callback functions
    DXUTSetCallbackDeviceCreated(OnCreateDevice);
    DXUTSetCallbackDeviceReset(OnResetDevice);
    DXUTSetCallbackDeviceLost(OnLostDevice);
    DXUTSetCallbackDeviceDestroyed(OnDestroyDevice);
    DXUTSetCallbackMsgProc(MsgProc);
    DXUTSetCallbackFrameRender(OnFrameRender);
    DXUTSetCallbackFrameMove(OnFrameMove);

	//Show the cursor and clip it when in full screen
    DXUTSetCursorSettings(true, true);

	//Initialize DXUT
	DXUTInit();
	DXUTCreateWindow(L"Surf Demo", NULL, NULL, NULL, 800, 600);

	//Enable NVIDIA NVPerfHUD
	UINT adapterToUse     = D3DADAPTER_DEFAULT;
	D3DDEVTYPE deviceType = D3DDEVTYPE_HAL;

	IDirect3D9* pD3D = DXUTGetD3DObject();
	int numberOfAdapters = pD3D->GetAdapterCount();
	for(int i = 0; i < numberOfAdapters; i++)        //For each adapater
	{
		//Look for the "NVIDIA NVPerfHUD" adapter
		D3DADAPTER_IDENTIFIER9 identifier;
		pD3D->GetAdapterIdentifier(i, 0, &identifier);
		if(strcmp(identifier.Description, "NVIDIA NVPerfHUD") == 0)
		{
			adapterToUse = i;
			enableNVHUD = true;
		}

	}

	//Create the device with new settings
	DXUTCreateDevice(adapterToUse, true, 0, 0, 0, ModifyDeviceSettings, 0);

	//Initialize application 
	initHUD();

	//Start the main loop
	DXUTMainLoop();

	m_dumpMemoryReport();

	return DXUTGetExitCode();

}

bool initHUD()
{
	//Get back buffer info (for sizing)
	const D3DSURFACE_DESC* pBackBufferDesc;
	pBackBufferDesc = DXUTGetBackBufferSurfaceDesc();

	hud.Init(&dialogResourceManager);
	hud.SetCallback(OnGUIEvent);

	//Settings (Upper left)
	int iY = 10; 
	hud.AddButton(IDC_HIDEINTERFACE, L"Hide Interface (F1)", 10, iY, 125, 22, VK_F1);
    hud.AddButton(IDC_TOGGLEFULLSCREEN, L"Toggle full screen", 10, iY+=24, 125, 22 );
    hud.AddButton(IDC_TAKESCREENSHOT, L"Take Screenshot(F3)", 10, iY += 24, 125, 22, VK_F3);

	//Render statistics (Upper Right)
	iY = 10;
	int rightSide = pBackBufferDesc->Width - 10 - 125;
	std::basic_string<TCHAR> testStr = L"FPS: ";
	hud.AddStatic(IDC_FPSDISPLAY, testStr.c_str(), rightSide, iY, 125, 22);

	//Sky controls (Bottom left
	hud.AddStatic(IDC_SKYSPEED, L"Sky Change Speed:", 10, pBackBufferDesc->Height - 30, 100, 20);
	hud.AddSlider(IDC_SKYTIMESLIDER, 120, pBackBufferDesc->Height - 30, 100, 20, 1, 60, 1);
	hud.AddStatic(IDC_SKYSPEEDPAUSE, L"Pause:", 240, pBackBufferDesc->Height - 30, 50, 20);
	hud.AddCheckBox(IDC_ENABLESKYTIME, L"", 290, pBackBufferDesc->Height - 30, 20, 20, true);

	hud.SetLocation(0, 0);
    hud.SetSize(170, 170 );
	return true;
}

bool initGround()
{
	//Get the device (and make sure it exists)
	IDirect3DDevice9* pD3DDevice;
	pD3DDevice = DXUTGetD3DDevice();
	if(!pD3DDevice)
		return false;

	int i = 0;

	//128 by 128 ground terrain
	SGroundVertex vertices[129*129];
	for(int z = 0; z < 129; z++)
	{
		for(int x = 0; x < 129; x++)
		{
			vertices[i].x = x*1.0f;
			vertices[i].y = 0.0f;
			vertices[i].z = z*1.0f;
			vertices[i].diffuse = D3DCOLOR_XRGB(255, 0, 0);
			vertices[i].tu = x / 128.0f;
			vertices[i].tv = z / 128.0f;
			i++;
		}
	}

	//Create the vertex buffer
	if(FAILED(pD3DDevice->CreateVertexBuffer(129*129*sizeof(SGroundVertex), 0, 
		                                     D3DFVF_GROUNDVERTEX, D3DPOOL_MANAGED, 
								             &pGroundVertexBuffer, NULL)))
		return false;

	//Copy data into vertex buffer
	VOID* pVertices;
	if(FAILED(pGroundVertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0)))
		return false;

	memcpy(pVertices, vertices, sizeof(vertices));

	pGroundVertexBuffer->Unlock();

	return true;
}

LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing, void* pUserContext)
{
	//Always allow dialog resource manager calls to handle global messages so GUI state is updated correctly
	*pbNoFurtherProcessing = dialogResourceManager.MsgProc(hWnd, uMsg, wParam, lParam);
    if(*pbNoFurtherProcessing)
        return 0;

	//Pass events to HUD
	*pbNoFurtherProcessing = hud.MsgProc(hWnd, uMsg, wParam, lParam);
    if(*pbNoFurtherProcessing)
        return 0;

	//Pass all remaining windows messages to camera so it can respond to user input
    camera.HandleMessages(hWnd, uMsg, wParam, lParam);

	return 0;
}

//--------------------------------------------------------------------------------------
// Rejects any devices that aren't acceptable by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsDeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, 
                                  D3DFORMAT BackBufferFormat, bool bWindowed, void* pUserContext )
{
    // Typically want to skip backbuffer formats that don't support alpha blending
    IDirect3D9* pD3D = DXUTGetD3DObject(); 
    if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
                    AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, 
                    D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
        return false;

    return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, const D3DCAPS9* pCaps, void* pUserContext )
{
	if(enableNVHUD)
		pDeviceSettings->DeviceType = D3DDEVTYPE_REF;

    return true;
}


//--------------------------------------------------------------------------------------
// Create any D3DPOOL_MANAGED resources here 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnCreateDevice(IDirect3DDevice9* pD3DDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext )
{
	dialogResourceManager.OnCreateDevice(pD3DDevice);

	//Init render objects
	initGround();
	sky.init();

	//Setup camera
	D3DXVECTOR3 vecEye(20.0f, 20.0f, 100.0f);
    D3DXVECTOR3 vecAt (100.0f, 0.0f, 120.0f);
    camera.SetViewParams(&vecEye, &vecAt);
	
	//Some render states
	pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
		
    return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3DPOOL_DEFAULT resources here 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnResetDevice(IDirect3DDevice9* pD3DDevice, 
                               const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext )
{
	dialogResourceManager.OnResetDevice();

	//Setup the projection matrix
	float fAspectRatio = pBackBufferSurfaceDesc->Width / (FLOAT)pBackBufferSurfaceDesc->Height;
	camera.SetProjParams(D3DX_PI/4, fAspectRatio, 0.1f, 2000.0f);

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( IDirect3DDevice9* pD3DDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	//Update the camera's position based on user input 
    camera.FrameMove(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Render the scene 
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameRender( IDirect3DDevice9* pD3DDevice, double fTime, float fElapsedTime, void* pUserContext)
{
    HRESULT hr;

	//Get the view and projection matrices from the camera and update them
	V(pD3DDevice->SetTransform(D3DTS_VIEW, camera.GetViewMatrix()));
	V(pD3DDevice->SetTransform(D3DTS_PROJECTION, camera.GetProjMatrix()));

    //Clear the render target and the zbuffer 
    V(pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 0, 50, 0), 1.0f, 0));

    //Render the scene
    if(SUCCEEDED(pD3DDevice->BeginScene()))
    {
		//Render the ground
		V(pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1));
		V(pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE));
		V(pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE));
		V(pD3DDevice->SetStreamSource(0, pGroundVertexBuffer, 0, sizeof(SGroundVertex)));
		V(pD3DDevice->SetFVF(D3DFVF_GROUNDVERTEX));
		V(pD3DDevice->DrawPrimitive(D3DPT_POINTLIST, 0, 129*129));

		//Render the sky
		sky.render(camera.GetEyePt());

		//Render the UI
		if(showHud)
			V(hud.OnRender(fElapsedTime));

        V(pD3DDevice->EndScene());
    }
}

//--------------------------------------------------------------------------------------
// Release resources created in the OnResetDevice callback here 
//--------------------------------------------------------------------------------------
void CALLBACK OnLostDevice(void* pUserContext)
{
	dialogResourceManager.OnLostDevice();
}


//--------------------------------------------------------------------------------------
// Release resources created in the OnCreateDevice callback here
//--------------------------------------------------------------------------------------
void CALLBACK OnDestroyDevice(void* pUserContext)
{
	dialogResourceManager.OnDestroyDevice();

	//Release buffers
	SAFE_RELEASE(pGroundIndexBuffer);
	SAFE_RELEASE(pGroundVertexBuffer);

	sky.cleanUp();
}

void CALLBACK OnGUIEvent( UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext )
{
    switch( nControlID )
    {
        case IDC_TOGGLEFULLSCREEN: 
			DXUTToggleFullScreen(); 
			break;
		case IDC_HIDEINTERFACE:
			showHud = !showHud;
			break;
    }
}